Ana içeriğe geç
  1. 100 Günde SwiftUI Notları/

13.Gün - Swift Protocol ve Extension

Swift Protocol Nasıl Oluşturulur ve Kullanılır? #

Protocol, Swift’te sözleşmelere benzer. Bir veri türünün ne tür işlevleri desteklemesini beklediğimizi tanımlamamızı sağlar, bu sayede kodumuzun geri kalanında bu kurallara uyabiliriz.

Evden işe giden birini simüle edeceğimiz bir kod düşünelim. Bir Car struct oluşturup ardından aşağıdaki methodu yazabiliriz.

func commute(distance: Int, using vehicle: Car) {
    // bazı kodlar....
}

İşe gitmek için tren de kullanıyor olabilir.

func commute(distance: Int, using vehicle: Train) {
    // bazı kodlar....
}

Otobüste kullanıyor olabilir.

func commute(distance: Int, using vehicle: Bus) {
    // bazı kodlar....
}

ya da bisiklet, scooter vs. de kullanıyor olabilir.

Aslında yolculuğun hangi vasıta ile gerçekleştiğiyle ilgilenmiyoruz. Önemsediğimiz şey; ne kadar sürdüğü ve hangi vasıtanın kullanıldığıdır aslında.

Protokoller burada devreye girer: kullanmak istediğimiz property ve methodları tanımlamamıza izin verir. Bu property ve methodları uygulamazlar sadece property ve methodların var olması gerektiğini söylerler.

Örneğin aşağıdaki gibi Vehicle protocol’ü tanımlayabiliriz.

protocol Vehicle {
    func estimateTime(for distance: Int) -> Int
    func travel(distance: Int)
}

Yukarıdaki kodu biraz inceleyelim;

  • Protocol oluşturabilmek için protocol yazar ardından protocol adını veriririz. Bu yeni bir tür olduğundan büyük harfle başlayan Camel case kullanmamız gerekir.
  • Protocol’ün içinde, protocol’ün beklediğimiz şekilde çalışması için ihtiyaç duyduğumuz tüm methodları listeliyoruz.
  • Bu methodların içinde herhangi bir kod bulunmaz, yani fonksiyonun gövdesi bulunmaz. Bunun yerine, method adlarını, parametreleri ve dönüş türlerini belirtiyoruz.
  • Gerekirse methodları throwing ve mutating olarak işaretleyebiliriz.

Protocol Nasıl Kullanılır? #

Artık bu protocol ile çalışan türler tasarlayabiliriz. Bu sayede söz konusu protocol’ün gerekliliklerini uygulayan yeni struct, class veya enumlar oluşturabiliriz. Bu süreç ise adopting veya conforming olarak adlandırılmaktadır.

Protocol var olması gereken tüm işlevselliği belirtmez, yalnızca asgari düzeyde bir işlevsellik belirtir. Protocol’e uygun yeni türler oluşturduğumuzda, gerekliliklerden başka property ve method ekleyebileceğimiz manasına gelmektedir.

Örneğin aşağıdaki gibi bir Vehicle protocol’üne uyumlu Car struct oluşturabiliriz.

struct Car: Vehicle {
    func estimateTime(for distance: Int) -> Int {
        distance / 50
    }

    func travel(distance: Int) {
        print("I'm driving \(distance)km.")
    }

    func openSunroof() {
        print("It's a nice day!")
    }
}

Yukarıdaki kodu inceleyelim;

  1. Tıpkı child sınıflarda yaptığımız gibi, Car adından sonra : iki nokta kullanrak Swift’e Car ’ın Vehicle ’a uygun olduğunu söyleriz.
  2. Vehicle protocol’de listelediğimiz tüm metotlar, Car struct’ında da aynen olmalıdır. Eğer farklı isimlere sahip olursa, farklı paremetreler kabul ederse farklı geri dönüş tipine sahip olurlarsa, Swift protocole uymadığımızı söyleyecektir.
  3. Car ’daki methodlar, protokolde belirlediğimiz methodların gerçekten uygulanmasını sağlar.
  4. openSunroof() protocol’den gelmemektedir. Daha öncede belirttiğimiz gibi, Car struct protocol’ün gerektirdiği tüm metotları uygulamıştır, fazladan kendi methodunu da ekleyebilir.

Daha önceki commute() fonksiyonumuzu son gelişmeler ile güncelleyelim.

func commute(distance: Int, using vehicle: Car) {
    if vehicle.estimateTime(for: distance) > 100 {
        print("That's too slow! I'll try a different vehicle.")
    } else {
        vehicle.travel(distance: distance)
    }
}

let car = Car()
commute(distance: 100, using: car)

//ÇIKTI:
//----------------------------------------
// I'm driving 100km.

Bu kodun tamamı çalışıyor fakat protocol herhangi bir değer katmadı. Evet, Car ’ın içinde iki özel method uygulamamızı sağladı, ancak bunu protokolü eklemeden de yapabilirdik öyleyse neden uğraşalım?

İşte protocol’lerin püf noktası : Swift, Vehicle protocol’üne uyan bir türün hem estimateTime() hem de travel() methodlarının uygulanması gerektiğini bilir ve bu nedenle paremetremizin türü olarak Car yerine Vehicle kullanmamıza izin verir. commute() fonksiyonunu şu şekilde yeniden yazabiliriz.

func commute(distance: Int, using vehicle: Vehicle) {

Şimdi bu fonksiyonun, Vehicle protokolüne uygun olduğu sürece herhangi bir veri türüyle çağrılabileceğini söylüyoruz. Fonksiyonun gövdesinin değiştirilmesine gerek yoktur.

Şimdi kodumuzu geliştirmeye devam edelim;

struct Bicycle: Vehicle {
    func estimateTime(for distance: Int) -> Int {
        distance / 10
    }

    func travel(distance: Int) {
        print("I'm cycling \(distance)km.")
    }
}

let bike = Bicycle()
commute(distance: 50, using: bike)

//ÇIKTI:
//----------------------------------------
// I'm cycling 50km.

Şimdi elimizde Vehicle ile uyumlu ikinci bir struct var ve protokollerin gücü burada ortaya çıkıyor. commute() fonksiyonuna artık bir Car ya da Bicycle ’ı parametre olarak geçebiliriz. Swift, estimateTime() veya travel() fonksiyonlardan bir çağrıldığında, otomatik olarak uygun olanı kullanacaktır.

Dolayısıyla protocol’ler, tam tür belirtmek yerine işlevsellik türü hakkında konuşmamıza izin verir. “Bu parametre araba olmalıdır” demek yerine “Bu parametre, seyahat süresini tahmin edebildiği ve yeni bir konuma geçebildiği sürece herhangi bir şey olabilir” diyebiliriz.

Protocol’ler ile methodların yanı sıra mutlaka bulunması gereken property’leri de belirtebiliriz. Bunu yapmak için var yazılır, property ismi yazılır, ardından bu property’nin okunabilir ve/veya yazılabilir olduğu yazılır.

Mevcut Vehicle protokolüne bazı property’leri ekleyelim.

protocol Vehicle {
    var name: String { get }
    var currentPassengers: Int { get set }
    func estimateTime(for distance: Int) -> Int
    func travel(distance: Int)
}

Bu iki özellik ekler;

  1. Okunabilir olması gereken name isimli bir String. Bu, bir sabit (constant) olduğu anlamına gelebilir, ancak getter ile hesaplanmış bir computed property’de olabilir.
  2. currentPassengers adında, hem okunabilir hem de yazılabilir bir tamsayı. Bu bir değişken olduğu anlamına gelebilir, ancak getter ve setter ile hesaplanmış bir özellik de olabilir.

Type annotation her iki property tanımlaması için de geçerlidir. Çünkü protocol’de varsayılan bir değer sağlayamayız, tıpkı protocol’de methodlar için fonksiyon gövdesi sağlayamadığımız gibi.

Protocol’de eklediğimiz iki property ile Car ve Bicycle struct’larında bu property’ler eksik olduğundan hata alacağız. Bunu düzeltmek için Car struct’a aşağıdakini ekleyelim.

let name = "Car"
var currentPassengers = 1

Bicycle struct’ına da aşağıdakini ekleyelim;

let name = "Bicycle"
var currentPassengers = 1

Artık protokolümüz gereği, protokolümüzü implemente eden her yapıda iki method ve iki property bulunmak zorundadır. Böylelikle bu işlevselliklerin olduğundan emin olarak kodumuzu yazabiliriz.

func getTravelEstimates(using vehicles: [Vehicle], distance: Int) {
    for vehicle in vehicles {
        let estimate = vehicle.estimateTime(for: distance)
        print("\(vehicle.name): \(estimate) hours to travel \(distance)km")
    }
}

İşte bu protocol’lerin gücünü gösteriyor. Bir Array olarak Vehicle protocol’ünü kabul ediyoruz. Bu da Vehicle protocol’ünü implemente eden herhangi bir yapıyı aktarabileceğimiz ve otomatik olarak çalışacağı anlamına geliyor.

getTravelEstimates(using: [car, bike], distance: 150)

//ÇIKTI:
//----------------------------------------
// Car: 3 hours to travel 150km
// Bicycle: 15 hours to travel 150km

Protokolleri parametre olarak kabul etmenin yanı sıra, gerekirse bir fonksiyondan protocol’de döndürebiliriz.

Sadece virgülle ayırarak istediğimiz kadar protocol’e uyabiliriz. Tek bir tane protocol ile sınırlı değiliz yani.
Ayrıca bir şeyin child sınıfını oluşturmamız ve bir protokole uymamız gerekirse, önce parent sınıfın adını yazmalı ardından protocol’ümüzü yazmamız gerekir.

Bu bölümde verilen kod örneklerinin toplu hali aşağıdadır.

import Cocoa

protocol Vehicle {
    var name: String { get }
    var currentPassengers: Int { get set }
    func estimateTime(for distance: Int) -> Int
    func travel(distance: Int)
}

struct Car: Vehicle {
    let name = "Car"
    var currentPassengers = 1
    
    func estimateTime(for distance: Int) -> Int {
        distance / 50
    }

    func travel(distance: Int) {
        print("I'm driving \(distance)km.")
    }

    func openSunroof() {
        print("It's a nice day!")
    }
}

struct Bicycle: Vehicle {
    let name = "Bicycle"
    var currentPassengers = 1
    
    func estimateTime(for distance: Int) -> Int {
        distance / 10
    }

    func travel(distance: Int) {
        print("I'm cycling \(distance)km.")
    }
}

func commute(distance: Int, using vehicle: Vehicle) {
    if vehicle.estimateTime(for: distance) > 100 {
        print("That's too slow! I'll try a different vehicle.")
    } else {
        vehicle.travel(distance: distance)
    }
}

func getTravelEstimates(using vehicles: [Vehicle], distance: Int) {
    for vehicle in vehicles {
        let estimate = vehicle.estimateTime(for: distance)
        print("\(vehicle.name): \(estimate) hours to travel \(distance)km")
    }
}

let car = Car()
commute(distance: 100, using: car)

let bike = Bicycle()
commute(distance: 50, using: bike)

getTravelEstimates(using: [car, bike], distance: 150)

Opaque Return Type Nedir? Nasıl Kullanılır? #

İki basit fonksiyonumuzun olduğunu varsayalım;

func getRandomNumber() -> Int {
    Int.random(in: 1...6)
}

func getRandomBool() -> Bool {
    Bool.random()
}

getRandomNumber() rastgele bir tamsayı ve getRandomBool() rastgele bir Boolean döndürür.

Hem Int hem de Bool , Equatable adı verilen ve “eşitlik için karşılaştırılabilir” anlamına gelen ortak bir Swift protocol’e uygundur. Equatable protocol == ifadesini kullanmamızı sağlayan şeydir.

print(getRandomNumber() == getRandomNumber())

Bu türlerin her ikisi de Equatable ’a uygun olduğundan, fonksiyonumuzu Equatable değeri döndürecek şekilde değiştirebiliriz.

func getRandomNumber() -> Equatable {
    Int.random(in: 1...6)
}

func getRandomBool() -> Equatable {
    Bool.random()
}

Ancak bu noktada kod çalışmayacak ve Swift bir hata mesajı verecektir : “protocol ‘Equatable’ can only be used as a generic constraint because it has Self or associated type requirements”. Bu hatanın anlamı Equatable döndürmenin mantıklı olmadığıdır ve neden mantıklı olmadığını anlamak Opaq Return Type’ı anlamanın anahtarıdır.

İlk olarak; fonksiyondan protocol döndürebiliriz.

Örneğin, kullanıcılar için kiralık araba bulan bir fonksiyonumuz olabilir. Bu fonksiyon kaç adet yolcu olduğu ve bagaj sayısı parametrelerini kabul edebilir ve birkaç struct’an birini geri döndürebilir; Compact, SUV, Minivan

Bu durumu, tüm bu struct’lar tarafından benimsenen Vehicle protocol’ü döndürerek halledebiliriz. Böylece fonksiyon çağıran kişi tüm araç çeşitlerini işlemek için 10 farklı fonksiyon yazmak yerine, istekleriyle eşleşen bir araç geri alacaktır. Bu araç türlerinin her biri Vehicle ’ın tüm method ve property’lerini uygulayacaktır, bu da birbirlerinin yerine kullanılabilecekleri anlamına gelir - kodlama açısından hangi seçenekleri geri aldığımızı umursamayız.

Şimdi bir Int veya bir Bool geri göndermeyi düşünelim. Evet her ikisi de Equatable ’a uygundur fakat birbirlerinin yerine kullanılamazlar ( bir Int ve bir Bool karşılaştırmak için == kullanamayız). Çünkü, Swift hangi protocol’e uyduklarına bakmaksızın bize izin vermez.

Bir fonksiyondan bir protocol döndürmek kullanışlıdır çünkü bilgileri gizlememize olanak tanır. Döndürülen tam türü belirtmek yerine, döndürülen işlevselliğe odaklanabiliriz. Bir Vehicle protocol söz konusu olduğunda bu, koltuk sayısını, yaklaşık yakıt kullanımını ve bir fiyatı geri bildirmek anlamına gelir. Bu da kodumuzu daha sonra bir şeyleri bozmadan değiştirebileceğimiz anlamına gelir. Vehicle ’ın gerektirdiği property ve methodları uyguladıkları sürece RaceCar veya bir PickUpTruck döndürebiliriz.

Bu şekilde bilgi gizlemek gerçekten yararlıdır, ancak Equatable ile mümkün değildir. Çünkü iki tamsayı elde etmek için getRandomNumber() fonksiyonunu iki kez çağırsak bile, tam veri türlerini gizlediğimiz için bunları karşılaştıramayız. (gerçekten karşılaştırılabilecek iki tamsayı olduğu gerçeğini gizledik.)

Opaque return types burada devreye girer ve kodumuzdaki bilgileri Swift derleyicisinden gizlemememizi sağlar. Bu sayede kodumuzu daha esnek hale getirebiliriz, gelecekte farklı şeyler döndürebiliriz ancak Swift her zaman döndürülen gerçek veri türünü anlar ve uygun şekilde kontrol eder.

İki fonksiyonumuzu opaque return type ‘a yükseltmek için dönüş tipinden önce some keyword’ünü ekleyelim.

func getRandomNumber() -> some Equatable {
    Int.random(in: 1...6)
}

func getRandomBool() -> some Equatable {
    Bool.random()
}

Artık getRandomNumber() fonksiyonunu iki kez çağırabilir ve == kullanarak sonuçları karşılaştırabiliriz. Bizim bakış açımızdan hala sadece Equatable var, ancak Swift perde arkasında bunların aslında iki tamsayı olduğunu biliyor.

Opaque return type döndürmek, belirli bir tür döndürmek yerine işlevselliğe odaklanabileceğimiz anlamına gelir. Böylelikle gelecekte kodumuzu Double.random(in:) olarak değiştirebiliriz ve hala kodumuzun harika çalıştığını görürürüz.

Ancak buradaki avantaj, Swift’in her zaman altta yatan gerçek veri türünü bilmesidir. Bu ince bir ayrımdır. Sadece Vehicle döndürmek “herhangi bir Vehicle türü ama ne olduğunu bilmiyoruz” anlamına gelirken, some Vehicle döndürmek “belirli bir Vehicle türü ama hangisi olduğunu söylemek istemiyoruz” anlamına gelir.

Örneğin SwiftUI’da some View gördüğümüzde aslında şu anlama geliyor; “bu düzenlemek için bir tür view gönderecek, ancak tam olarak ne olduğunu yazmak istemiyorum- bunu kendiniz çözün (çünkü bunu yazmak istesem gerçekten çok uzun bir tür yazmam gerekecekti)”

Swift Extension Nasıl Oluşturulur ve Kullanılır? #

Extension, ister biz oluşturmuş olalım, ister başkası hatta Apple’ın kendi türlerinden biri olsun, herhangi bir türe işlevsellik eklememize olanak tanır.

Extension ‘ı incelemek için String’ler üzerinde çalışan trimmingCharacters(in:) methodunu kullanalım. Bu method alfanümerik karakterler, ondalık sayılar boşluk gibi karakterleri String’in başından veya sonundan kaldırabilir.

Örneğin burada her iki tarafında boşluk olan bir String var;

var quote = "   The truth is rarely pure and never simple   "

quote String’inin her iki tarafındaki boşlukları kırpmayı şu şekilde yapabiliriz;

let trimmed = quote.trimmingCharacters(in: .whitespacesAndNewlines)

.whitespacesAndNewlines değeri, Apple’ın Foundation API’sinden geliyor.

Her seferinde trimmingCharacters(in:) ifadesini çağırmak biraz kelime kalabalığı yaratıyor, bu yüzden bunu daha kısa hale getirmek için bir uzantı yazalım;

extension String {
    func trimmed() -> String {
        self.trimmingCharacters(in: .whitespacesAndNewlines)
    }
}

Yukarıdaki kodu biraz inceleyelim;

  1. Mevcut bir türe işlevsellik eklemek istediğimizi söyleyen extension keyword’ü ile başlıyoruz.
  2. Hangi tür? Burada String ’e işlevsellik eklemek istiyoruz.
  3. Ardından { } parantezlerimizi açıyoruz ve String’e eklemek istediğimiz işlevselliğe başlıyoruz.
  4. Yeni bir String döndüren, trimmed() methodunu ekliyoruz.
  5. Methodun içinde, daha önce olduğu gibi aynı yöntemi çağırıyoruz, trimmingCharacters(in:) sonucu geri döndürüyor.
  6. self ’i nasıl kullandığımıza dikkat edelim; bu otomatik olarak geçerli string’i ifade eder. Bu mümkün çünkü şu anda bir String extension’dayız.

Artık, boşlukları kaldırmak istediğimizde her yerde aşağıdakini yazabiliriz.

let trimmed = quote.trimmed()

Extension’ın Avantajları #

  1. qoute. yazdığımızda Xcode bize önerilerde bulunur, fonksiyonumuzu extension olarak eklediğimizde fonksiyonumuz bu öneriler arasında gösterilir.
  2. Global fonksiyonları kullanmak kodumuzu dağınık hale getirir, organize edilmesi ve takibi zordur. Diğer taraftan, extension genişlettikleri veri türüne göre gruplandırılır.
  3. Extension methodlar, orijinal türün bir parçası olduğundan, türün dahili verilerine tam erişime sahip olurlar. Böylelikle private access contorol’e sahip property ve methodlar kullanılabilir.

Ayrıca extension değerleri yerinde değiştirmeyi yani, yeni bir değer döndürmek yerine bir değeri doğrudan değiştirmeyi kolaylaştırır.

Örneğin, daha önce boşlukları kaldırıp yeni bir String döndüren trimmed() methodunu yazmıştık, ancak String’i doğrudan değiştirmek istersek bunu extension’a ekleyebiliriz.

mutating func trim() {
    self = self.trimmed()
}

Not: String bir struct olduğundan ve biz de String içinde extension yazdığımızdan methodumuzu mutating olarak işaretlemeliyiz.

quote String’i değişken (var) olarak oluşturulduğundan, şu şekilde boşukların ı silebiliriz;

quote.trim()

Yeni bir değer döndürdüğümüzde trimmed() adını kullandık, ancak String’i doğrudan değiştirdiğimizde trim() adlandırmasını kullandık. Bu Swift tasarım yönergelerinin bir parçasıdır: Eğer yeni bir değeri değiştirmek yerine geri döndürüyorsanız reversed() gibi ed veya ing gibi kelime sonları kullanmalıyız.

Türlere property eklemek için de extension kullanabiliriz fakat bir kural vardır. Bu property’ler yalnızca computed (hesaplanan) property olmalıdır, depolanan (stored) değil. Bunun nedeni, yeni stored property’lerin, veri türünün gerçek boyutunu etkileyecek olmasıdır. Bir Int ’e bir grup stored property eklersek, her yerdeki Int sayının bellekte daha fazla yer kaplaması gerekir bu da öngöremediğimiz bir sürü soruna sebebiyet verebilir.

Fakat computed property kullanarak hala çok şey yapabiliriz. Örneği çok satırlı bir String’in satır sayısını sayabiliriz. Bu satır sayısını da lines ismini verdiğimiz computed porperty vasıtasıyla ortaya çıkarabiliriz.

extension String {
    var lines: [String] {
        self.components(separatedBy: .newlines)
    }
}

Yukarıdaki extension’ı uyguladıktan sonra, herhangi bir String’in lines property’sini aşağıdaki gibi okuyabiliriz.

let lyrics = """
But I keep cruising
Can't stop, won't stop moving
It's like I got this music in my mind
Saying it's gonna be alright
"""

print(lyrics.lines.count)

//ÇIKTI:
//----------------------------------------
// 4

Struct Extension ve Initializer #

Struct’larda otomatik olarak memberwise initializer’ın var olduğundan bahsetmiştik. Swift Struct’ın property’lerine bakarak bizim için initializer oluşturmaktaydı.

struct Book {
    let title: String
    let pageCount: Int
    let readingHours: Int
}

let lotr = Book(title: "Lord of the Rings", pageCount: 1178, readingHours: 24)

Eğer custom initializer oluşturursak, Swift otomatik oluşturulan memberwise initializer’ın kullanımını devredışı bırakacaktır.

struct Book {
    let title: String
    let pageCount: Int
    let readingHours: Int

    init(title: String, pageCount: Int) {
        self.title = title
        self.pageCount = pageCount
        self.readingHours = pageCount / 50
    }
}

Fakat bazı durumlarda hem memberwise initializer’ı hem de custom initializer’ı beraber kullanmak isteyebiliriz. İşte bu durumda extension’ı kullanarak bunu gerçekleştirebiliriz. custom initializer’ı Struct’ı ilk tanımladığımız yerde değilde extension olarak kullanıp orada oluşturursak, hem custom hem memberwise initializer’ı elde etmiş oluruz.

extension Book {
    init(title: String, pageCount: Int) {
        self.title = title
        self.pageCount = pageCount
        self.readingHours = pageCount / 50
    }
}

double initializer

Protocol Extension Nasıl Oluşturulur ve Kullanılır? #

Protocol’ler uyulması gereken sözleşmeler oluşturmamızı sağlar, extension ise mevcut tiplere işlevsellik eklememize izin verir. Peki protocol üzerine extension yazabilseydik ne olurdu?

Protocol extension sayesinde, protocol’ü genişleterek implemente edilecek methodlar ekleyebiliriz bu sayede protocol’ü benimseyen tüm türler bu methodlara sahip olabilir.

Bir örnek ile başlayalım;

let guests = ["Mario", "Luigi", "Peach"]

if guests.isEmpty == false {
    print("Guest count: \(guests.count)")
}

Bazı insanlar ise yukardaki kod yerine ! operatörünü kullanmayı tercih edebilir.

if !guests.isEmpty {
    print("Guest count: \(guests.count)")
}

Bu iki yaklaşım yerine extension’da kullanabiliriz.

extension Array {
    var isNotEmpty: Bool {
        isEmpty == false
    }
}

Artık anlaşılması daha kolay olan aşağıdaki kodu yazabiliriz.

if guests.isNotEmpty {
    print("Guest count: \(guests.count)")
}

Ama daha iyisini yapabiliriz. isNotEmpty ’yi sadece Array’lere ekledik, peki ya Set ve Dictionary’ler? Kendimizi tekrar edip her yere bu extension’ı yazmaktan daha iyi bir çözüm var. Array, Set ve Dictionary’nin tümü Collection adı verilen built-in (yerleşik) bir protocol’e uygundur ve bu protocol aracılığıyla contains() , sorted() , reversed() , gibi methodlara sahip olurlar.

Collection aynı zamanda isEmpty property’sini de içerir. Dolayısıyla, Collection üzerine bir extension yazarsak, isEmpty property’sine erişmeye devam edebiliriz.

extension Collection {
    var isNotEmpty: Bool {
        isEmpty == false
    }
}

Yukarıdaki extension sayesinde, isNotEmpty ’yi Array, Set ve Dictionary’nin yanı sıra Collection protocol’e uyan diğer türlerde de kullanabiliyoruz.

Protocol’e extension ekleyerek, protocol’ü benimseyen tüm yapılara işlevsellik ekleyebiliyoruz. Bu aslında Apple’ın protocol-oriented programming olarak adlandırdığı bir tekniği kullanmamızı sağlar.

Örneğin şu şekilde bir protocol’üm olduğunu varsayalım;

//PROTOCOL !
protocol Person {
    var name: String { get }
    func sayHello()
}

Bu protocol’e uyan tüm türlerin sayHello() methodunu uygulaması gerekir. Fakat bu methodun default (varsayılan) implementasyonunu aşağıdaki gibi ekleyebiliriz.

//EXTENSION !
extension Person {
    func sayHello() {
        print("Hi, I'm \(name)")
    }
}

Artık protocol’e uyan tüm türler isterlerse sayHello() methodunu ekleyebilir fakat zorunlu değildirler. Her zaman protocol extension içinde sağlanan methoda güvenebilirler.

Böylece sayHello() methodu olmadan bir employee oluşturabiliriz.

struct Employee: Person {
    let name: String
}

Employee struct’ı Person protocol’üne uyduğu için varsayılan sayHello() yöntemini rahatlıkla kullanabiliriz.

let taylor = Employee(name: "Taylor Swift")
taylor.sayHello()

//ÇIKTI:
//----------------------------------------
// Hi, I'm Taylor Swift

100 Days of SwiftUI Checkpoint - 8 #


Bu yazıyı İngilizce olarak da okuyabilirsiniz.
You can also read this article in English.

Bu yazı, SwiftUI Day 13 adresinde bulunan yazılardan kendim için aldığım notları içermektedir. Orjinal dersi takip etmek için lütfen bağlantıya tıklayın.